aviutl2\generic\binding/
project.rs

1/// プロジェクトファイルにデータを保存・取得するための構造体。
2pub struct ProjectFile {
3    pub(crate) internal: *mut aviutl2_sys::plugin2::PROJECT_FILE,
4}
5
6/// プロジェクトファイルのデータ取得・保存に関するエラー。
7#[derive(thiserror::Error, Debug)]
8pub enum ProjectFileError {
9    #[error("key contains null byte: {0}")]
10    KeyContainsNull(std::ffi::NulError),
11    #[error("data retrieval failed for key {0}")]
12    RetrievalFailed(String),
13    #[error("data length exceeds 4096 bytes, got {0} bytes")]
14    DataTooLarge(usize),
15    #[error("value contains null byte: {0}")]
16    ValueContainsNull(std::ffi::NulError),
17}
18
19impl ProjectFile {
20    /// 生ポインタから`ProjectFile`を作成します。
21    ///
22    /// # Safety
23    ///
24    /// - `raw`は有効な`PROJECT_FILE`ポインタである必要があります。
25    pub unsafe fn from_raw(raw: *mut aviutl2_sys::plugin2::PROJECT_FILE) -> Self {
26        Self { internal: raw }
27    }
28
29    /// プロジェクトに保存されている文字列を取得します。
30    ///
31    /// # Errors
32    ///
33    /// - `key`にヌル文字が含まれている場合、失敗します。
34    /// - 文字列が見つからなかった場合は失敗します。
35    pub fn get_param_string(&self, key: &str) -> Result<String, ProjectFileError> {
36        let c_key = std::ffi::CString::new(key).map_err(ProjectFileError::KeyContainsNull)?;
37        unsafe {
38            let raw_str = ((*self.internal).get_param_string)(c_key.as_ptr() as _);
39            if raw_str.is_null() {
40                return Err(ProjectFileError::RetrievalFailed(key.to_string()));
41            }
42            Ok(std::ffi::CStr::from_ptr(raw_str)
43                .to_string_lossy()
44                .into_owned())
45        }
46    }
47
48    /// プロジェクトに保存されているバイナリデータを取得します。
49    ///
50    /// # Errors
51    ///
52    /// - `key`にヌル文字が含まれている場合、失敗します。
53    /// - `data` の長さが保存されているデータの長さと一致しない場合、失敗します。
54    /// - 指定されたキーに対応するデータが存在しない場合、失敗します。
55    pub fn get_param_binary(&self, key: &str, data: &mut [u8]) -> Result<(), ProjectFileError> {
56        let success = unsafe {
57            let key = std::ffi::CString::new(key).map_err(ProjectFileError::KeyContainsNull)?;
58            ((*self.internal).get_param_binary)(
59                key.as_ptr() as _,
60                data.as_mut_ptr() as _,
61                data.len() as _,
62            )
63        };
64        if !success {
65            return Err(ProjectFileError::RetrievalFailed(key.to_string()));
66        }
67        Ok(())
68    }
69
70    /// プロジェクトに文字列を保存します。
71    ///
72    /// # Errors
73    ///
74    /// key、valueにヌル文字が含まれている場合、失敗します。
75    pub fn set_param_string(&mut self, key: &str, value: &str) -> Result<(), ProjectFileError> {
76        let key_cstr = std::ffi::CString::new(key).map_err(ProjectFileError::KeyContainsNull)?;
77        let value_cstr =
78            std::ffi::CString::new(value).map_err(ProjectFileError::ValueContainsNull)?;
79        unsafe {
80            ((*self.internal).set_param_string)(key_cstr.as_ptr() as _, value_cstr.as_ptr() as _);
81        }
82        Ok(())
83    }
84
85    /// プロジェクトにバイナリデータを保存します。
86    ///
87    /// # Errors
88    ///
89    /// - `data` の長さが4096バイトを超える場合、失敗します。
90    /// - `key`にヌル文字が含まれている場合、失敗します。
91    pub fn set_param_binary(&mut self, key: &str, data: &[u8]) -> Result<(), ProjectFileError> {
92        if data.len() > 4096 {
93            return Err(ProjectFileError::DataTooLarge(data.len()));
94        }
95        unsafe {
96            let key = std::ffi::CString::new(key).map_err(ProjectFileError::KeyContainsNull)?;
97            ((*self.internal).set_param_binary)(
98                key.as_ptr() as _,
99                data.as_ptr() as _,
100                data.len() as _,
101            );
102        }
103        Ok(())
104    }
105
106    /// プロジェクトに保存されているデータをすべて削除します。
107    pub fn clear_params(&mut self) {
108        unsafe { ((*self.internal).clear_params)() }
109    }
110}
111
112#[cfg(feature = "serde")]
113static NAMESPACE: &str = "--aviutl2-rs";
114
115#[cfg(feature = "serde")]
116impl ProjectFile {
117    /// プロジェクトにデータをシリアライズして保存します。
118    ///
119    /// # Note
120    ///
121    /// 今現在の実装ではデータはMessagePackにシリアライズされた後にZstdで圧縮されています。
122    ///
123    /// # Errors
124    ///
125    /// - シリアライズに失敗した場合。
126    /// - 圧縮に失敗した場合。
127    pub fn serialize<T: serde::Serialize>(&mut self, key: &str, value: &T) -> crate::AnyResult<()> {
128        let bytes = rmp_serde::to_vec_named(value)?;
129        let bytes = zstd::encode_all(&bytes[..], 0)?;
130        let num_bytes = bytes.len();
131        self.set_param_string(key, &format!("{NAMESPACE}:serde-zstd-v1:{}", num_bytes))?;
132        for (i, chunk) in bytes.chunks(4096).enumerate() {
133            let chunk_key = format!("{NAMESPACE}:serde-zstd-v1:chunk:{}:{}", key, i);
134            self.set_param_binary(&chunk_key, chunk)?;
135        }
136        Ok(())
137    }
138
139    /// プロジェクトからデータをデシリアライズして取得します。
140    pub fn deserialize<T: serde::de::DeserializeOwned>(&self, key: &str) -> crate::AnyResult<T> {
141        let header = self.get_param_string(key)?;
142        let header_prefix = format!("{NAMESPACE}:serde-zstd-v1:");
143        let num_bytes = header
144            .strip_prefix(&header_prefix)
145            .ok_or_else(|| anyhow::anyhow!("invalid header for key {}", key))?;
146        let num_bytes: usize = num_bytes.parse()?;
147        if num_bytes == 0 {
148            anyhow::bail!("invalid data length 0 for key {}", key);
149        }
150        let mut bytes = Vec::with_capacity(num_bytes);
151        let mut read_bytes = 0;
152        let mut chunk = vec![0u8; 4096];
153        for i in 0.. {
154            let chunk_key = format!("{NAMESPACE}:serde-zstd-v1:chunk:{}:{}", key, i);
155            let to_read = std::cmp::min(4096, num_bytes - read_bytes);
156            chunk.resize(to_read, 0);
157            match self.get_param_binary(&chunk_key, &mut chunk) {
158                Ok(()) => {
159                    bytes.extend_from_slice(&chunk);
160                    read_bytes += to_read;
161                    if read_bytes >= num_bytes {
162                        break;
163                    }
164                }
165                Err(_) => break,
166            }
167        }
168        anyhow::ensure!(
169            read_bytes == num_bytes,
170            "incomplete data for key {}, expected {} bytes, got {} bytes",
171            key,
172            num_bytes,
173            read_bytes
174        );
175        let decompressed_bytes = zstd::decode_all(&bytes[..])?;
176        let value: T = rmp_serde::from_slice(&decompressed_bytes)?;
177        Ok(value)
178    }
179}